/*
 * $Id: locale.c,v 1.33 2005-12-26 07:41:48 bacon Exp $
 */

#include <xp/bas/locale.h>
#include <xp/bas/utf8.h>
#include <xp/bas/mstring.h>
#include <xp/bas/wstring.h>
#include <xp/bas/memory.h>
#include <xp/bas/errno.h>

#include <locale.h>

xp_size_t (*xp_wcmclen) (xp_wchar_t wc) = xp_unicode_utf8_len;
xp_size_t (*xp_wctomc) (
	xp_wchar_t wc, xp_mchar_t* mc, xp_size_t mcsz) = xp_unicode_to_utf8;
xp_size_t (*xp_mctowc) (
	const xp_mchar_t* mc, xp_size_t mcsz, xp_wchar_t* wc) = xp_utf8_to_unicode;
xp_size_t (*xp_mcseqlen) (xp_mchar_t mc) = xp_utf8_sequence_len;

int xp_setlocale ()
{
	/* TODO: write your own setlocale function and 
	 *       possibly support more locales.
	 */
#ifdef _WIN32
	return 0;
#else
	char* locale = setlocale (LC_ALL, "");
	if (locale == NULL) return -1;

#ifdef XP_CHAR_IS_WCHAR
	if (xp_mstrstr(locale, ".utf8") == XP_NULL && 
	    xp_mstrstr(locale, ".UTF8") == XP_NULL &&
	    xp_mstrstr(locale, ".utf-8") == XP_NULL &&
	    xp_mstrstr(locale, ".UTF-8") == XP_NULL) {
		xp_seterrno (XP_ENOTSUP);
		return -1;
	}
#endif

	return 0;
#endif
}

/* 
 * returns number of bytes when converted to mcs 
 */
xp_size_t xp_wcsmclen (const xp_wchar_t* wcs)
{
	const xp_wchar_t* p = wcs;
	xp_size_t len = 0;

	while (*p != XP_WCHAR('\0')) {
		len += xp_wcmclen (*p);
		p++;
	}

	return len;
}

/*
 * returns number of bytes when converted to mcs
 */
xp_size_t xp_wcsnmclen (const xp_wchar_t* wcs, xp_size_t wcslen)
{
	const xp_wchar_t* p = wcs, * end = wcs + wcslen;
	xp_size_t len = 0;

	while (p < end) {
		len += xp_wcmclen (*p);
		p++;
	}

	return len;
}

/* 
 * returns the number of characters converted. if xp_strlen(wcs) is 
 * greater than the returned number, the wcs string has not been fully
 * converted. (xp_size_t)-1 is returned when the mcs buffer is too
 * small to hold even a terminating null character.
 */
xp_size_t xp_wcstomcs (
	const xp_wchar_t* wcs, xp_mchar_t* mcs, xp_size_t mcssz)
{
	const xp_wchar_t* p = wcs;
	xp_size_t n, nx, len = mcssz;

	/*nx = xp_wcmclen(XP_WCHAR('\0'));*/
	nx = xp_sizeof(xp_mchar_t);
	if (len < nx) {
		xp_seterrno (XP_ENOBUFS);
		return (xp_size_t)-1;
	}

	len -= nx; 
	while (*p != XP_WCHAR('\0') && len > 0) {
		n = xp_wctomc (*p, mcs, len);
		if (n == 0) { /* buffer to small? */
			/*xp_wctomc (XP_WCHAR('\0'), mcs, nx);*/
			*mcs = XP_MCHAR('\0');
			return p - wcs; 
		}
		mcs += n; len -= n; p++;
	}

	/*xp_wctomc (XP_WCHAR('\0'), mcs, nx);*/
	*mcs = XP_MCHAR('\0');
	return p - wcs;
}

/* 
 * returns the number of characters converted. if wcslen is greater than
 * the returned number, the wcs string has not been fully converted.
 * the mcs buffer is not guranteed to be null terminated unless a null
 * is included in the wcs string within the wcslen length.
 */
xp_size_t xp_wcsntomcs (
	const xp_wchar_t* wcs, xp_size_t wcslen, 
	xp_mchar_t* mcs, xp_size_t* mcssz)
{
	const xp_wchar_t* p = wcs;
	const xp_wchar_t* end = wcs + wcslen;
	xp_size_t n, len = *mcssz;

	while (p < end) {
		n = xp_wctomc (*p, mcs, len);
		if (n == 0) { /* buffer too small? */
			*mcssz -= len;
			return p - wcs; 
		}
		mcs += n; len -= n; p++;
	}
	
	*mcssz -= len;
	return p - wcs;
}

/*
 * returns the number of characters. if the sequence is illegal,
 * (xp_size_t)-1 is returned.
 */
xp_size_t xp_mcswclen (const xp_mchar_t* mcs)
{
	xp_wchar_t wc;
	xp_size_t n, mlen, wclen = 0;
	const xp_mchar_t* p = mcs;
	
	while (*p != XP_MCHAR('\0')) p++;
	mlen = p - mcs;
	
	for (p = mcs; mlen > 0; p += n, mlen -= n) {
		n = xp_mctowc (p, mlen, &wc);
		/* insufficient input or wrong sequence */
		if (n == 0) {
			xp_seterrno (XP_EILSEQ);
			return (xp_size_t)-1;
		}
		wclen++;
	}

	if (*p != XP_MCHAR('\0')) {
		xp_seterrno (XP_EILSEQ);
		return (xp_size_t)-1;
	}

	return wclen;
}

/*
 * returns the number of characters. if the sequence is illegal,
 * (xp_size_t)-1 is returned.
 */
xp_size_t xp_mcsnwclen (const xp_mchar_t* mcs, xp_size_t mcslen)
{
	xp_wchar_t wc;
	xp_size_t n, mlen = mcslen, wclen = 0;
	const xp_mchar_t* p = mcs;
	
	for (p = mcs; mlen > 0; p += n, mlen -= n) {
		n = xp_mctowc (p, mlen, &wc);
		/* insufficient input or wrong sequence */
		if (n == 0) {
			xp_seterrno (XP_EILSEQ);
			return (xp_size_t)-1;
		}
		wclen++;
	}

	return wclen;
}

/* 
 * returns the number of bytes converted. if xp_mstrlen(mcs) is greater
 * than the returned number, the mcs string is not fully converted.
 * (xp_size_t)-1 is returned when a conversion error occurs.
 * an error occurs when wcssz == 0 or an illegal sequence is encountered.
 */
xp_size_t xp_mcstowcs (
	const xp_mchar_t* mcs, xp_wchar_t* wcs, xp_size_t wcssz)
{
	xp_wchar_t wc;
	xp_size_t n, mlen;
	const xp_mchar_t* p = mcs, * p1 = mcs;
	xp_wchar_t* q = wcs, * wcend = wcs + wcssz - 1;

	if (q > wcend) {
		xp_seterrno (XP_ENOBUFS);
		return (xp_size_t)-1;
	}

	while (*p != XP_MCHAR('\0')) p++;
	mlen = p - mcs;
	
	for (p = mcs; mlen > 0; p += n, mlen -= n) {
		n = xp_mctowc (p, mlen, &wc);
		/* insufficient input or wrong sequence */
		if (n == 0) {
			xp_seterrno (XP_EILSEQ);
			return (xp_size_t)-1;
		}
		if (q < wcend) { *q++ = wc; p1 += n; }
	}

	if (*p != XP_MCHAR('\0')) {
		xp_seterrno (XP_EILSEQ);
		return (xp_size_t)-1;
	}

	*q = XP_WCHAR('\0');
	return p1 - mcs;
}

/* 
 * returns the number of bytes converted. if mcslen is greater than the
 * returned number, the mcs string is not fully converted.
 * (xp_size_t)-1 is returned when a conversion error occurs.
 * an error occurs when an illegal sequence is encountered.
 */
xp_size_t xp_mcsntowcs (
	const xp_mchar_t* mcs, xp_size_t mcslen,
	xp_wchar_t* wcs, xp_size_t* wcssz)
{
	xp_wchar_t wc;
	xp_size_t n, mlen = mcslen;
	const xp_mchar_t* p = mcs, * p1 = mcs;
	xp_wchar_t* q = wcs, * wcend = wcs + *wcssz - 1;

	if (q > wcend) {
		*wcssz = 0;
		return 0;
	}

	for (p = mcs; mlen > 0; p += n, mlen -= n) {
		n = xp_mctowc (p, mlen, &wc);
		/* insufficient input or wrong sequence */
		if (n == 0) {
			xp_seterrno (XP_EILSEQ);
			return (xp_size_t)-1;
		}
		if (q < wcend) { *q++ = wc; p1 += n; }
	}

	*wcssz = q - wcs; 
	return p1 - mcs;
}

int xp_wcstomcs_strict (
	const xp_wchar_t* wcs, xp_mchar_t* mcs, xp_size_t mcssz)
{
	xp_size_t n;

	n = xp_wcstomcs (wcs, mcs, mcssz);
	if (n == (xp_size_t)-1) return -1;
	if (n < xp_wstrlen(wcs)) {
		xp_seterrno (XP_ENOBUFS);
		return -1;
	}

	return 0;
}

int xp_mcstowcs_strict (
	const xp_mchar_t* mcs, xp_wchar_t* wcs, xp_size_t wcssz)
{
	xp_size_t n;

	n = xp_mcstowcs (mcs, wcs, wcssz);
	if (n == (xp_size_t)-1) return -1;
	if (n < xp_mstrlen(mcs)) {
		xp_seterrno (XP_ENOBUFS);
		return -1;
	}

	return 0;
}

xp_mchar_t* xp_wcstomcs_dynamic (const xp_wchar_t* wcs)
{
	xp_mchar_t* mcs;
	xp_size_t n;

	n = xp_wcsmclen (wcs);
	if (n == (xp_size_t)-1) return XP_NULL;

	mcs = xp_malloc (xp_sizeof(xp_mchar_t) * (n + 1));
	if (mcs == XP_NULL) return XP_NULL;

	xp_wcstomcs (wcs, mcs, n + 1);
	return mcs;
}

xp_wchar_t* xp_mcstowcs_dynamic (const xp_mchar_t* mcs)
{
	xp_wchar_t* wcs;
	xp_size_t n;

	n = xp_mcswclen (mcs);
	if (n == (xp_size_t)-1) return XP_NULL;

	wcs = xp_malloc (xp_sizeof(xp_wchar_t) * (n + 1));
	if (wcs == XP_NULL) return XP_NULL;

	xp_mcstowcs (mcs, wcs, n + 1);
	return wcs;
}
